package example.players;

import java.util.Random;

import roshambo.Player;

public class FabianPlayer implements Player {

    public static final int WIN = 1, TIE = 2, LOSE = 3;

    private double minCertaintyRatio = 0.1;
    private double loseChangeRatio = 0.7;
    private double loseDeceiveRatio = 0.4;
    private double midGameRatio = 0.1;
    private double lateGameRatio = 0.4;
    private double loseRatio;

    private int[] oMoves;
    private int[] moveCount;
    private double[] frequencies;

    private int[] results;
    private int[] resultsCounter;

    private int totalMoves;
    private int gamesToBePlayed;
    private int myLastMove;

    private int shortTermMemory = 1000;
    private int deceiveCount;

    private boolean activateRandom;
    private Random random;

    public FabianPlayer() {
        random = new Random();
    }

    @Override
    public int play() {
        if (activateRandom || totalMoves <= shortTermMemory) {
            myLastMove = playRandomStrategy();
        } else {
            myLastMove = playFrequencyStrategy();
        }

        return myLastMove;
    }

    @Override
    public void receive(int result) {
        oMoves[totalMoves] = result;
        moveCount[result]++;

        //Define if it was a win, tie or lose
        int res = TIE;
        if (myLastMove == PEDRA) {
            if (result == TESOURA) {
                res = WIN;
            } else if (result == PAPEL) {
                res = LOSE;
            }
        } else if (myLastMove == PAPEL) {
            if (result == PEDRA) {
                res = WIN;
            } else if (result == TESOURA) {
                res = LOSE;
            }
        } else {
            if (result == PAPEL) {
                res = WIN;
            } else if (result == PEDRA) {
                res = LOSE;
            }
        }

        results[totalMoves] = res;
        resultsCounter[res]++;

        //Check if we should deceive
        if (deceiveCount <= 0 && (double) totalMoves / gamesToBePlayed > midGameRatio) {
            loseRatio = getLastMovesLoseRatio(shortTermMemory);
            if (loseRatio > loseDeceiveRatio) {
                deceiveCount = 50; //lets play different for a while
            }
        }

        //If we are losing badly, change to random
        if ((double) totalMoves / gamesToBePlayed > lateGameRatio) {
            loseRatio = (double) resultsCounter[LOSE] / totalMoves;
            if (loseRatio > loseChangeRatio) {
                activateRandom = true;
            }
        }

        totalMoves++;
    }

    @Override
    public void newGame(int games) {
        oMoves = new int[games];
        moveCount = new int[4];
        frequencies = new double[4];

        results = new int[games];
        resultsCounter = new int[4];

        totalMoves = 0;
        deceiveCount = 0;
        myLastMove = 0;
        activateRandom = false;
        gamesToBePlayed = games;
    }

    private int playFrequencyStrategy() {
        int move = 0;

        //Moves frequency order
        frequencies[PEDRA] = (double) moveCount[PEDRA] / totalMoves;
        frequencies[PAPEL] = (double) moveCount[PAPEL] / totalMoves;
        frequencies[TESOURA] = (double) moveCount[TESOURA] / totalMoves;

        int mostPlayed = 0;
        int midPlayed = 0;
        int lessPlayed = 0;

        if (frequencies[PEDRA] >= frequencies[PAPEL] && frequencies[PAPEL] >= frequencies[TESOURA]) {
            mostPlayed = PEDRA;
            midPlayed = PAPEL;
            lessPlayed = TESOURA;
        } else if (frequencies[PEDRA] >= frequencies[TESOURA] && frequencies[TESOURA] >= frequencies[PAPEL]) {
            mostPlayed = PEDRA;
            midPlayed = TESOURA;
            lessPlayed = PAPEL;
        } else if (frequencies[PAPEL] >= frequencies[PEDRA] && frequencies[PEDRA] >= frequencies[TESOURA]) {
            mostPlayed = PAPEL;
            midPlayed = PEDRA;
            lessPlayed = TESOURA;
        } else if (frequencies[PAPEL] >= frequencies[TESOURA] && frequencies[TESOURA] >= frequencies[PEDRA]) {
            mostPlayed = PAPEL;
            midPlayed = TESOURA;
            lessPlayed = PEDRA;
        } else if (frequencies[TESOURA] >= frequencies[PEDRA] && frequencies[PEDRA] >= frequencies[PAPEL]) {
            mostPlayed = TESOURA;
            midPlayed = PEDRA;
            lessPlayed = PAPEL;
        } else {
            mostPlayed = TESOURA;
            midPlayed = PAPEL;
            lessPlayed = PEDRA;
        }

        if (deceiveCount > 0) { //we are temporarily changing strategy 
            move = playRandomStrategy();
            deceiveCount--;
        } else {
            //Check certainty
            double certaintyRatio = frequencies[mostPlayed] - frequencies[midPlayed];

            //If we have "enough" certainty about the next possible move
            if (certaintyRatio >= minCertaintyRatio) {
                move = selectWinnerAgainst(mostPlayed);
            } else { //Play "safe"	
                certaintyRatio = frequencies[midPlayed] - frequencies[lessPlayed];
                if (certaintyRatio >= minCertaintyRatio) {
                    move = selectLoserAgainst(lessPlayed);
                } else {
                    move = playRandomStrategy();
                }
            }
        }

        return move;
    }

    private double getLastMovesLoseRatio(int mem) {
        int loseCount = 0;
        for (int i = totalMoves; i > totalMoves - mem; i--) {
            if (i < 0) {
                break;
            }
            if (results[i] == LOSE) {
                loseCount++;
            }
        }

        return (double) loseCount / mem;
    }

    private int selectWinnerAgainst(int m) {
        int ret = 0;

        //Retornar a jogada que perde contra o lessPlayedChoice
        switch (m) {
            case PEDRA:
                ret = PAPEL;
                break;
            case PAPEL:
                ret = TESOURA;
                break;
            case TESOURA:
                ret = PEDRA;
                break;
        }

        return ret;
    }

    private int selectLoserAgainst(int m) {
        int ret = 0;

        //Retornar a jogada que perde contra o lessPlayedChoice
        switch (m) {
            case PEDRA:
                ret = TESOURA;
                break;
            case PAPEL:
                ret = PEDRA;
                break;
            case TESOURA:
                ret = PAPEL;
                break;
        }

        return ret;
    }

    private int playRandomStrategy() {
        return random.nextInt(3) + 1;
    }

}
